package main

import (
	"context"
	"flag"
	"fmt"
	"math/big"
	"sort"
	"time"

	"gitee.com/thubcc/blockchain/constant"
	"gopkg.in/fatih/set.v0"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/ethclient"
)

var (
	rpcUrl   = flag.String("url", "http://localhost:8545", "rpc raw path")
	maxBlock = flag.Int("max", 50, "max block be checked")
)

type Blocks []*types.Block

func (m Blocks) Len() int {
	return len(m)
}

func (m Blocks) Swap(i, j int) {
	m[i], m[j] = m[j], m[i]
}

func (m Blocks) Less(i, j int) bool {
	return m[i].NumberU64() < m[j].NumberU64()
}

type Accounts struct {
	reserved set.Interface
	inUsed   set.Interface
}

func NewAccounts(res []string) *Accounts {
	ret := new(Accounts)
	ret.reserved = set.New(set.ThreadSafe)
	ret.inUsed = set.New(set.ThreadSafe)
	for _, v := range res {
		ret.reserved.Add(v)
	}
	return ret
}

func (a *Accounts) Add(item string) {
	if !a.IsReserved(item) {
		a.inUsed.Add(item)
	}
}

func (a *Accounts) IsReserved(item string) bool {
	return a.reserved.Has(item)
}

type toBeSort struct {
	addresses []string
	balance   map[string]*big.Int
}

func NewToBeSort(addr set.Interface, get func(string) *big.Int) *toBeSort {
	as := make([]string, addr.Size())
	for k, s := range addr.List() {
		as[k] = s.(string)
	}
	bal := make(map[string]*big.Int)
	for _, a := range as {
		bal[a] = get(a)
	}
	return &toBeSort{as, bal}
}

func (a *toBeSort) Len() int {
	return len(a.addresses)
}

func (a *toBeSort) Swap(i, j int) {
	a.addresses[i], a.addresses[j] = a.addresses[j], a.addresses[i]
}

func (a *toBeSort) Less(i, j int) bool {
	return a.balance[a.addresses[i]].Cmp(a.balance[a.addresses[j]]) > 0
}

func (a *Accounts) SortBalance(getBalance func(string) *big.Int) []string {
	tobe := NewToBeSort(a.inUsed, getBalance)
	sort.Sort(tobe)
	return tobe.addresses
}

func main() {

	flag.Parse()

	var reservedAccounts []string

	for _, v := range constant.ReservedAccounts {
		reservedAccounts = append(reservedAccounts, common.HexToAddress(v).Hex())
	}

	c, err := ethclient.Dial(*rpcUrl)
	if err != nil {
		fmt.Println(err)
		panic("conect client failure")
	}
	defer c.Close()

	ctx := context.TODO()

	var number *big.Int = nil
	var accounts = NewAccounts(reservedAccounts)
	var lastmining = set.New(set.ThreadSafe)
	var miners Blocks

	for count := 0; count < *maxBlock; count++ {

		block, err := c.BlockByNumber(ctx, number)
		if err != nil {
			fmt.Println(err)
			panic("get block by number failure")
		}

		if number == nil {
			number = block.Number()
		}

		coinbase := block.Coinbase().Hex()
		accounts.Add(coinbase)

		transactions := block.Transactions()
		for tc, tx := range transactions {
			from, err := c.TransactionSender(ctx, tx, block.Hash(), uint(tc))
			if err != nil {
				fmt.Println(err)
				panic("recover sender failure")
			}

			accounts.Add(from.Hex())

			if to := tx.To(); to != nil {
				accounts.Add(to.Hex())
			}
		}
		if !accounts.IsReserved(coinbase) {
			if !lastmining.Has(coinbase) {
				lastmining.Add(coinbase)
				miners = append(miners, block)
			}
		}

		if number.Sign() == 0 {
			break
		} else {
			number.Sub(number, big.NewInt(1))
		}
	}

	sort.Sort(miners)

	for _, v := range miners {
		fmt.Printf("%5d block miner is %s ", v.NumberU64(), v.Coinbase().Hex())
		fmt.Println("at", time.Unix(int64(v.Time()), 0))
	}

	space := "                                                        "

	sorted := accounts.SortBalance(func(add string) *big.Int {
		b, _ := c.BalanceAt(ctx, common.HexToAddress(add), nil)
		return b
	})

	for k, addr := range sorted {
		xaddr := common.HexToAddress(addr)
		v, _ := c.BalanceAt(ctx, xaddr, nil)
		if v.Cmp(big.NewInt(0)) > 0 {
			bal := v.String()
			countTx, err := c.NonceAt(ctx, xaddr, nil)
			if err != nil {
				fmt.Println(k, err)
				panic("get account nonce failure")
			}
			fmt.Printf("%4d. [%s] %s Wei %4d Tx\n", k, addr, space[:(30-len(bal))]+bal, countTx)
		}
	}
}
